package com.xr.handle;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.xr.annotation.RemoteService;
import com.xr.util.Assert;
import com.xr.util.SpringContextHolder;
import com.xr.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Arnold Yand
 * @summary process the remote service call from consumer
 * @time 2018/3/27 16:18
 */
public class RemoteServiceConsumerHandle extends InstantiationAwareBeanPostProcessorAdapter {

  private static Logger logger = LoggerFactory.getLogger(RemoteServiceConsumerHandle.class);
  private static ConcurrentHashMap<String, Object> initedService = new ConcurrentHashMap<String, Object>();

  private String serviceGroup;
  private String serviceVersion;

  // Application Info
  private ApplicationConfig application = null;

  // Registry Info
  private RegistryConfig registry = null;

  public RemoteServiceConsumerHandle(String applicationName, String registyAddress) {
    application = new ApplicationConfig();
    application.setName(applicationName + "_Consumer");

    registry = new RegistryConfig();
    registry.setAddress(registyAddress);
  }

  /**
   * 实例化bean之后调用
   *
   * @param bean
   * @param beanName
   * @return
   * @throws BeansException
   */
  @Override
  public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    boolean result = super.postProcessAfterInstantiation(bean, beanName);
    processRemoteServiceCall(bean);
    return result;
  }

  /**
   * 扫描bean中使用了RemoteService注解的字段，根据注解规则生成引用对象
   *
   * @param bean
   */
  private void processRemoteServiceCall(final Object bean) {
    ReflectionUtils.doWithFields(bean.getClass(),
            field -> {
              RemoteService rsAnnotation = AnnotationUtils.getAnnotation(field, RemoteService.class);
              if (rsAnnotation == null) {
                return;
              }

              /**
               * 如果是本地优先，则返回本地对象
               * 本地没有时，调用远程对象
               */
              String fieldName = field.getName();
              Class referenceInterface = field.getType();
              String beanId = StringUtil.isEmpty(rsAnnotation.value()) ? fieldName : rsAnnotation.value();
              Object targetObject = null;
              if (rsAnnotation.isRemote()) {
                targetObject = initRemoteService(referenceInterface);
              } else {
                try {
                  targetObject = SpringContextHolder.getBean(beanId);
                } catch (Exception e) {
                  logger.error(e.getMessage(), e);
                }

                Assert.notNull(targetObject, "can not find the local bean for field:" + fieldName);
              }

              if (targetObject != null) {
                ReflectionUtils.makeAccessible(field);
                field.set(bean, targetObject);
              }
            });
  }

  private <T> T initRemoteService(Class referenceInterface) {

    Object targetService = initedService.get(referenceInterface.getName());

    if (targetService != null) {
      return (T) targetService;
    }


    // Refer remote service
    ReferenceConfig reference = new ReferenceConfig();
    reference.setApplication(this.application);
    reference.setRegistry(this.registry);
    reference.setInterface(referenceInterface);
    reference.setScope(Constants.SCOPE_REMOTE);
    reference.setGroup(this.serviceGroup);
    reference.setVersion(this.serviceVersion);
    reference.setCheck(false);
    reference.setTimeout(600000);
    reference.setRetries(0);
//    reference.setFilter("CustomerFilter");
//    reference.setMethods(annotation.getMethodConfigs());
    T target = (T)reference.get();
    initedService.put(referenceInterface.getName(), target);
    return target;
  }

  public String getServiceGroup() {
    return serviceGroup;
  }

  public void setServiceGroup(String serviceGroup) {
    this.serviceGroup = serviceGroup;
  }

  public String getServiceVersion() {
    return serviceVersion;
  }

  public void setServiceVersion(String serviceVersion) {
    this.serviceVersion = serviceVersion;
  }
}
